<?php
require_once 'Core/DocDb/Couch/Exception.php';
require_once 'Zend/Http/Client/Exception.php';

class Core_DocDb_Couch
{
	/**
	 * The Protocol to use. Supported are HTTP or HTTPS
	 * 
	 * @var string
	 */
	private $_protocol = 'http';
	
	/**
	 * The CouchDB Host
	 * 
	 * @var string
	 */
	private $_host = '127.0.0.1';
	
	/**
	 * The port on which we're connecting to CouchDB
	 * 
	 * @var string
	 */
	private $_port = '5984';
	
	/**
	 * Username for couchdb authentication.
	 * 
	 * @var string
	 */
	private $_username;
	
	/**
	 * Password for couchdb authentication.
	 * 
	 * @var string
	 */
	private $_password;
	
	/**
	 * The name of the database
	 * 
	 * @var string
	 */
	private $_database;
	
	/**
	 * Factory for Core_DocDb_Couch classes.
	 * 
	 * The first argument is the configuration for the couchDB connector
	 * to create. It may either be an instance of a Zend_Config object or
	 * an associative array of key/value pairs that map the following parameters:
	 * 
	 * host: hostname of the couchdb server
	 * port: port of the couchdb server
	 * username: username, if any.
	 * password: password, if any.
	 *
	 * @param  mixed $config Key/Value pair of configuration elements, or Zend_Config object.
	 * @return Core_DocDb_Couch
	 * @throws Core_DocDb_Couch_Exception
	 */
	public static function factory ( $config = array() )
	{
		// Construct an empty couch DB object
		$couch_db = new Core_DocDb_Couch( $config );
		return $couch_db;
	}
	
	/**
	 * Constructor. Creates a new instance of a CouchDB object.
	 * 
	 * @param  mixed $config Key/Value pair of configuration elements, or Zend_Config object.
	 * @throws Core_DocDb_Couch_Exception
	 */
	public function __construct ( $config = array() )
	{
		if( $config instanceof Zend_Config )
		{
			$config = $config->toArray();
		}
		
		/*
         * Verify that adapter parameters are in an array.
         */
		if( ! is_array( $config ) )
		{
			throw new Core_DocDb_Couch_Exception( 'Adapter parameters must be in an array or a Zend_Config object' );
		}
		
		// Sanitize our incoming information
		$this->_host = isset( $config['host'] ) ? $config['host'] : $this->_host;
		$this->_port = isset( $config['port'] ) ? $config['port'] : $this->_port;
		$this->_username = isset( $config['username'] ) ? $config['username'] : $this->_username;
		$this->_password = isset( $config['password'] ) ? $config['password'] : $this->_password;
		$this->_database = isset( $config['database'] ) ? $config['database'] : $this->_database;
		$this->_protocol = in_array( $config['protocol'], array('http', 'https') ) ? $config['protocol'] : $this->_protocol;
	}
	
	/**
	 * Query URI Construction helper.
	 * 
	 * @param string $path The CouchDB Query Path.
	 * @param array $params Any URI Parameters that should be sent.
	 */
	private function _getUri ( $path = '/', $params = array() )
	{
		if( !empty( $this->_username ) )
		{
			$uri_string = '%1$s://%5$s:%6$s@%2$s:%3$s/%4$s%7$s?%8$s';
		} else
		{
			$uri_string = '%1$s://%2$s:%3$s/%4$s%7$s?%8$s';
		}
		
		$param_string = http_build_query( $params );
		$uri = sprintf( $uri_string, $this->_protocol, $this->_host, $this->_port, $this->_database, $this->_username, $this->_password, $path, $param_string );
		
		return $uri;
	}
	
	/**
	 * Write an item to the document store.
	 * 
	 * @param string $id The CouchDB unique identifier to delete.
	 * @param object $values The value object to write to the database. It will be JSON encoded.
	 * @throws Core_DocDb_Couch_Exception
	 */
	public function create ( $id, $values )
	{
		try
		{
			
			// Construct the URL
			$uri = $this->_getUri( "/$id" );
			
			// Construct our client.
			$client = new Zend_Http_Client();
			$client->setUri( $uri );
			$client->setHeaders( 'Content-Type', 'application/json' );
			$client->setRawData( json_encode( $values ), 'application/json' );
			
			// Call our method. This will throw exceptions that may be caught by a client user.
			$result = $client->request( 'PUT' );
			$result_body = $result->getBody();
			
			return json_decode( $result_body, true );
		
		} catch( Zend_Http_Client_Exception $z_e )
		{
			// Error thrown by our client call. Map it to our own error and throw it.
			throw new Core_DocDb_Couch_Exception( $z_e->getMessage(), $z_e->getCode() );
		}
	}
	
	/**
	 * This method reads a record from CouchDB.
	 * 
	 * @param string $id The CouchDB unique identifier to delete.
	 * @param array $params Any additiona parameters that may affect the query. Revision, view, etc.
	 * @throws Core_DocDb_Couch_Exception
	 */
	public function read ( $id, $params = array() )
	{
		try
		{
			// Construct the URL
			$uri = $this->_getUri( "/$id", $params );
			
			// Construct our client.
			$client = new Zend_Http_Client();
			$client->setUri( $uri );
			
			// Call our method. This will throw exceptions that may be caught by a client user.
			$result = $client->request( 'GET' );
			$result_body = $result->getBody();
			
			return json_decode( $result_body, true );
		
		} catch( Zend_Http_Client_Exception $z_e )
		{
			// Error thrown by our client call. Map it to our own error and throw it.
			throw new Core_DocDb_Couch_Exception( $z_e->getMessage(), $z_e->getCode() );
		}
	}
	
	/**
	 * This method updates a record in the document store.
	 * 
	 * @param string $id The CouchDB unique identifier to delete.
	 * @param object $values The revision to delete. If left null, will delete the most recent revision. It will be JSON encoded.
	 * @throws Core_DocDb_Couch_Exception
	 */
	public function update ( $id, $values )
	{
		try
		{
			// Construct the URL
			$uri = $this->_getUri( "/$id" );
			
			// Construct our client.
			$client = new Zend_Http_Client();
			$client->setUri( $uri );
			$client->setHeaders( 'Content-Type', 'application/json' );
			$client->setRawData( json_encode( $values ), 'application/json' );
			
			// Call our method. This will throw exceptions that may be caught by a client user.
			$result = $client->request( 'PUT' );
			$result_body = $result->getBody();
			
			return json_decode( $result_body, true );
		
		} catch( Zend_Http_Client_Exception $z_e )
		{
			// Error thrown by our client call. Map it to our own error and throw it.
			throw new Core_DocDb_Couch_Exception( $z_e->getMessage(), $z_e->getCode() );
		}
	}
	
	/**
	 * This method deletes a record from CouchDB.
	 * 
	 * @param string $id The CouchDB unique identifier to delete.
	 * @param string $revision The revision to delete. If left null, will delete the most recent revision.
	 * @throws Core_DocDb_Couch_Exception
	 */
	public function delete ( $id, $revision = null )
	{
		try
		{
			// Sanitize our revision
			if( empty( $revision ) )
			{
				$result = $this->get( $id );
				$revision = $result['_rev'];
			}
			
			// Construct the URL
			$uri = $this->_getUri( "/$id", array('rev' => $revision) );
			
			// Construct our client.
			$client = new Zend_Http_Client();
			$client->setUri( $uri );
			
			// Call our method. This will throw exceptions that may be caught by a client user.
			$result = $client->request( 'DELETE' );
			$result_body = $result->getBody();
			
			return json_decode( $result_body, true );
		
		} catch( Core_DocDb_Couch_Exception $c_e )
		{
			// One of the upstream methods threw an error. Rethrow it.
			throw $c_e;
		
		} catch( Zend_Http_Client_Exception $z_e )
		{
			// Error thrown by our client call. Map it to our own error and throw it.
			throw new Core_DocDb_Couch_Exception( $z_e->getMessage(), $z_e->getCode() );
		}
	}
}